# OSWorkflow核心概念
- 项目地址:https://github.com/ailohq/osworkflow
OSWorkflow(Open Symphony Workflow)是一个灵活的、可扩展的、基于Java的工作流引擎。它的工作流引擎是基于有限状态机(Finite State Machine,简称FSM)概念实现的。有限状态机是一种抽象的计算模型,用来表示和控制执行流程。在OSWorkflow中,有限状态机用于描述工作流的执行过程和状态转换。
在OSWorkflow中,每个工作流都被定义为一个有限状态机。每个状态机包含一系列的状态(State,即步骤step+状态status)、转换(Transition)和动作(Action)。状态表示工作流的当前位置,转换表示状态之间的流转,动作则是引导从一个状态到另一个状态的行为。
以下是OSWorkflow基于有限状态机的实现的基本步骤:
- 定义工作流:在OSWorkflow中,工作流是通过XML文件进行定义的。这个XML文件描述了工作流的所有状态、转换和动作。
- 创建工作流实例:当需要启动一个新的工作流时,OSWorkflow引擎会根据工作流定义创建一个新的工作流实例。
- 执行动作:当工作流实例在某个状态时,可以执行一个或多个动作。这些动作可能会改变工作流实例的状态,引导工作流实例向前流转。
- 状态转换:当执行动作后,工作流实例的状态可能会发生改变。这个改变就是一个状态转换。状态转换是由动作触发的。
- 条件判断:在执行动作和状态转换时,OSWorkflow引擎会进行条件判断。只有当满足特定条件时,动作才会被执行,状态才会发生转换。
- 结束工作流:当工作流实例达到终止状态时,工作流就结束了。
通过以上步骤,OSWorkflow引擎实现了基于有限状态机的工作流管理。这种方式使得工作流的流程和规则变得清晰明了,便于理解和管理。下面我们将进一步了解它的实现原理。
正常一个OSWorkflow工作流包含多个步骤。每一个步骤都有一个当前状态(例如,:Queued,、Underway,或 Finished)。每一个步骤中都有一个或者多个动作可以被执行。每一个动作都可以设置执行条件(condition),也可以设置执行函数(pre-function 或 post-function)。动作产生的结果(result)会导致工作流的状态和当前步骤发生改变。
工作流的xml定义
如下是一个OSWorkflow工作流定义的组成部份。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE workflow PUBLIC "-//Open Source Workflow//DTD OSWf 3.0//EN"
"http://oswf.sourceforge.net/OSWf-3.0.dtd">
<workflow>
<initial-actions>
.
.
.
</initial-actions>
<steps>
<step>
<actions>
<action>
<results>
<results>
<unconditional-result old-status="xxx" status="yy" step="zz"/>
<conditional-result .../>
</results>
</results>
</action>
...
</actions>
</step>
.
.
</steps>
</workflow>
首先是标准的XML头部,要注意的是OSWorkflow将会通过这些指定的DTD来验证XML内容的合法性。你可以使用绝大多数的XML编辑工具来编辑它。
workflow表示一个工作流
initial-actions定义初始化步骤。初始化步骤是一种特殊类型的步骤,它用来启动工作流。在一个工作流程开始前,它是没有状态的,不处在任何一个步骤,用户必须采取某些动作才能开始这个流程。这些特殊步骤被定义在
。例如下面的: 这个动作只是简单的说明了下一个我们要去的步骤和状态。触发动作action发生后,工作流会流转到步骤1,并且状态会变为Queued。
<initial-actions> <action id="1" name="Start Workflow"> <results> <unconditional-result old-status="Finished" status="Queued" step="1"/> </results> </action> </initial-actions>
下面,我们进一步分析其他构成元素。
# step(步骤)
Step表示工作流所处的位置,一个 Step 里面可以有一个或多个 Action(即actions数组)。执行哪一个action则取决于用户、外部事件或自动触发器。而一个工作流由多个Step来表示流程。
从一个step触发可以流转到另一个Step,也可以在同一个step内部流转(因为一个step可以有多个状态status)。
<workflow>
<steps>
<step>
<actions>
<action></action>
...
</actions>
</step>
<step></step>
...
</steps>
</workflow>
# action(动作)
action表示动作,每个步骤都有一个或多个action。
action 触发发生在 step 内或 step 之间的流转,或者说是基于 State 的流转。action 和step 之间的关系是,step 说明“在哪里”,action 说明“去哪里”。 每一个动作至少有一个无条件结果(unconditional result)和零到多个条件结果(conditional result) 。例如下面的例子:
该步骤叫First Draft
,它定义了2个动作,分别是:Start First Draft
和Finish First Draft
。每个步骤都有一个默认的无条件结果unconditional-result
。
其中old-status属性是用来指明当前步骤完成以后的状态是什么,大部分情况下通常用"Finished"表示。
而status属性表示该流程实例的状态status,status的取值可以是Underway或者Queued。
由于下面定义的两个动作没有任何限制,所以用户可以任意调用一个动作都可以。
<step id="1" name="First Draft">
<actions>
<action id="1" name="Start First Draft">
<results>
<unconditional-result old-status="Finished" status="Underway" step="1"/>
</results>
</action>
<action id="2" name="Finish First Draft">
<results>
<unconditional-result old-status="Finished" status="Queued" step="2"/>
</results>
</action>
</actions>
</step>
<step id="2" name="finished" />
正常来说上面应该要先完成Start First Draft
,才能开始Finish First Draft
。但是上面因为没有做限制导致没有先后顺序。
要解决上面的问题,我们可以通过下面的condition条件做约束。
# condition(条件)
条件(Condition)是用于控制工作流转换(Transition)是否可以发生的关键组件。条件的结果为真(true)时,转换可以发生;结果为假(false)时,转换无法发生。每个动作可以设置0个或多个约束条件。
在OSWorkflow中内置了很多条件,例如下面我们会用到的com.opensymphony.workflow.util.StatusCondition
条件。
com.opensymphony.workflow.util.StatusCondition
用于检查工作流实例的状态。具体来说,它会比较工作流实例当前步骤(Step)的状态与预定义的状态值,以确定条件是否满足。如果当前步骤的状态与预定义的状态值相等,则条件结果为真(true),否则为假(false)。
要使用 StatusCondition,需要在工作流定义文件(例如 workflow.xml)中添加一个条件元素(condition element),并指定其类型为 com.opensymphony.workflow.util.StatusCondition
。然后,需要配置一个参数(argument),用于指定预定义的状态值。例如下面我们对Start First Draft
动作添加condition条件。
<action id="1" name="Start First Draft">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
<arg name="status">Queued</arg>
</condition>
</conditions>
</restrict-to>
<results>
<unconditional-result old-status="Finished" status="Underway" step="1"/>
</results>
</action>
其中的<restrict-to>
标签定义了一组条件,只有当这些条件都满足时,这个动作才能被执行。上面的条件定义保证了action动作1只能在当前状态为“Queued”的时候才能被调用,也就是说在初始化动作被调用以后。
接下来,我们想在一个用户开始Start First Draft
以后,设置他为“owner”。为了达到这样的目的,我们需要做2件事情:
- 通过一个函数设置
caller
变量在当前的环境设置里。 - 根据
caller
变量来设置owner
属性。com.opensymphony.workflow.util.Caller
是Osworkflow提供的内置函数,该函数获得当前调用工作流的用户,并放入一个名为caller的字符型变量中。
<action id="1" name="Start First Draft">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
<arg name="status">Queued</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished" status="Underway" step="1" owner="${caller}"/>
</results>
</action>
这段 XML 定义了一个动作,这个动作在工作流实例的当前步骤的状态为 "Queued" 时可以被执行,执行的结果是将工作流实例的状态改为 "Underway",并将工作流实例移动到步骤 1。
我们也对动作2设置条件,如下:
<action id="2" name="Finish First Draft">
<restrict-to>
<conditions type="AND">
<condition type="class">
<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
<arg name="status">Underway</arg>
</condition>
<condition type="class">
<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
</condition>
</conditions>
</restrict-to>
<results>
<unconditional-result old-status="Finished" status="Queued" step="2"/>
</results>
</action>
在这段XML定义中,<restrict-to>
使用了一个类型为 "AND" 的条件组合,表示这两个条件都需要满足才能执行。
- 第一个使用 StatusCondition 类来检查工作流实例的状态。具体来说,只有当工作流实例的当前步骤的状态为 "Underway" 时,这个动作才能被执行。
- 第二个使用AllowOwnerOnlyCondition 类来检查当前用户是否为步骤的所有者。只有当当前用户是步骤的所有者时,这个动作才能被执行。
这段 XML 定义了一个动作,这个动作在工作流实例的当前步骤的状态为 "Underway" 且当前用户是步骤的所有者时可以被执行,执行的结果是将工作流实例的状态改为 "Queued",并将工作流实例移动到步骤 2。
# pre-function、post-function(前置函数、后置函数)
# action动作中的函数定义
在 OSWorkflow 中,每个动作(Action)都可以配置前置函数(Pre-function)和后置函数(Post-function)。这些函数在动作执行的前后被调用,用于执行一些特定的操作,例如修改工作流实例的属性、验证数据、发送通知等。
![image-20240318225137351](./img/4.2 osworkflow-action函数调用.png)
- 前置函数(Pre-function):这些函数在动作执行前被调用。它们通常用于准备动作的执行环境,例如设置动作的输入参数、验证数据的有效性、检查权限等。如果前置函数执行失败(例如数据验证失败),动作将不会被执行。
- 后置函数(Post-function):这些函数在动作执行后被调用。它们通常用于处理动作的执行结果,例如更新工作流实例的状态、保存数据、发送通知等。如果后置函数执行失败(例如数据保存失败),动作的执行结果可能会被回滚。
在 OSWorkflow 的 XML 定义中,前置函数和后置函数使用 <pre-functions>
和 <post-functions>
标签进行配置。每个函数都有一个类型(type)和一组参数(arg)。类型用于指定函数的实现类,参数用于配置函数的输入参数。例如:
<action id="1" name="Approve">
<pre-functions>
<function type="class">
<arg name="class.name">com.example.workflow.MyPreFunction</arg>
</function>
</pre-functions>
<post-functions>
<function type="class">
<arg name="class.name">com.example.workflow.MyPostFunction</arg>
</function>
</post-functions>
...
</action>
在这个例子中,动作 "Approve" 有一个前置函数 MyPreFunction 和一个后置函数 MyPostFunction。在动作执行前,MyPreFunction 将被调用;在动作执行后,MyPostFunction 将被调用。
# step步骤中的函数定义
如果是在一个 step 中定义的 pre-function 和 post-function,用法会有所不同,pre-function 将会在工作流流转到这个 step 之前执行。注意这个 pre-function 将会在任何目的是此步骤的流 转发生之前执行,甚至是这个步骤本身(举个例子,如果状态从 Queued 转换到 Finished 但并 未改变步骤也会执行 pre-function)。 同样的,step的post-functions 也将会在工作流传递出这个步骤之前调用,甚至当它自己改变状 态但步骤不发生改变也是如此。 下图的图解说明了调用顺序。注意 action 虚线内做了一些抽象,它自己也可以有 per-function 和 post-function。
![image-20240318224915637](./img/4.2 osworkflow种step的函数调用.png)
# result(结果)
result(结果)是用于描述工作流动作(action)执行后的输出。它定义了工作流实例(workflow instance)在动作执行完成后应该转移到的下一个步骤(step),以及与之相关的状态、所有者和其他属性的变更。result 在工作流定义文件(例如 workflow.xml)中的 <results>
标签内进行配置。
对于每一个动作,都要求至少存在一个结果,称为 Unconditional- Result(无条件结果)。结果只不过是一系列指令,告诉流程引擎下一步是什么。这涉及在构成给定工作流的状态机中从一个步骤到下一步的转换。
在 OSWorkflow 中,有两种类型的结果:
- 无条件结果(Unconditional Result):这是最常见的结果类型,表示在动作执行后,无论条件是否满足,都会发生的结果。无条件结果使用
<unconditional-result>
标签进行定义。例如:
<unconditional-result old-status="InProgress" status="Completed" step="2"/>
在这个例子中,无条件结果将工作流实例的状态从 "InProgress" 更改为 "Completed",并将实例移动到步骤 2。
- 条件结果(Conditional Result):这种结果类型表示只有在满足某些条件时才会发生的结果。条件结果使用
<conditional-result>
标签进行定义,并在其中配置一个或多个条件(condition)。例如:
<conditional-result>
<conditions>
<condition type="class">
<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
<arg name="status">Approved</arg>
</condition>
</conditions>
<result old-status="InProgress" status="Completed" step="2"/>
</conditional-result>
在这个例子中,条件结果会检查工作流实例的当前步骤状态是否为 "Approved"。如果满足该条件,结果将工作流实例的状态从 "InProgress" 更改为 "Completed",并将实例移动到步骤 2。
执行结果后的三种选择
在 OSWorkflow 中,执行完 result(结果)后,工作流实例(workflow instance)可能会转移到三种不同的情况:到达 step(步骤)、到达 split(分支)和到达 join(聚合)。下面分别介绍这三种情况:
到达 step(步骤):这是最常见的情况,表示工作流实例在执行完动作(action)后,到达了一个新的步骤。步骤是工作流中的一个节点,用于表示工作流实例的状态和执行位置。在执行完 result 后,工作流实例会根据 result 的配置,将其状态、所有者等属性进行更改,并转移到下一个步骤。例如:
<unconditional-result old-status="InProgress" status="Completed" step="2"/>
在这个例子中,执行完 result 后,工作流实例的状态从 "InProgress" 更改为 "Completed",并转移到步骤 2。
到达 split(分支):这种情况表示工作流实例在执行完动作后,到达了一个分支节点。分支节点用于在工作流中创建多个并行执行路径,从而实现复杂的业务逻辑。在到达分支节点后,工作流实例会根据分支节点的配置,同时进入多个步骤,并在这些步骤中并行执行。例如:
<unconditional-result old-status="InProgress" status="Completed" split="2"/> ... <splits> <split id="2"> <unconditional-result old-status="Finished" step="2" status="Underway" /> <unconditional-result old-status="Finished" step="3" status="Underway" /> </split> </splits>
在这个例子中,执行完 result 后,工作流实例的状态从 "InProgress" 更改为 "Completed",并转移到分支节点 2。
到达 join(聚合):这种情况表示工作流实例在执行完动作后,到达了一个聚合节点。聚合节点用于将多个并行执行路径重新合并为一个路径,从而实现对工作流实例的同步。在到达聚合节点后,工作流实例会等待所有并行执行路径都完成,然后根据聚合节点的配置,将其状态、所有者等属性进行更改,并转移到下一个步骤。例如:
<!-- for step id 6 -> <unconditional-result join="1"/> ... <!- for step id 8 -> <unconditional-result join="1"/> ... <joins> <join id="1"> <conditions type="AND"> <condition type="beanshell"> <arg name="script"> "Finished".equals(jn.getStep(6).getStatus() && "Finished".equals(jn.getStep(8).getStatus()) </arg> </condition> </conditions> <unconditional-result old-status="Finished" status="Underway" step="2"/> </join> </joins>
我们定义了一个 ID 为 1 的聚合节点。这个聚合节点有一个条件,使用 BeanShell 脚本来检查步骤 6 和步骤 8 的状态是否都为 "Finished"。只有当这两个步骤的状态都为 "Finished" 时,这个聚合节点才会被执行。
执行聚合节点后,有一个无条件结果,将工作流实例的状态更改为 "Underway",并将实例移动到步骤 2。
# OSWorkflow FSM核心方法剖析
# 工作流案例
下面,我们通过创建一个简单的工作流,追踪它执行动作的代码过程来进行分析。
首先应该载入流程定义文件创建定义工作流的文件(在 XML 里面)。
在开始载入流程定义、调用动作以前,我们需要配置OSWorkflow的数据存储方式和定义文件的位置等。
osworkflow.xml
下面指明了我们准备使用内存 (MemoryWorkflowStore) 来保存流程数据。这样可以减少设置数据库的相关信息,减少出问题的可能性。用内存持久化对于测试来说是非常方便的。
<osworkflow>
<persistence
class="com.opensymphony.workflow.spi.memory.MemoryWorkflowStore" />
<factory
class="com.opensymphony.workflow.loader.XMLWorkflowFactory">
<property key="resource" value="workflows.xml" />
</factory>
</osworkflow>
上面的配置文件还指明了我们工作流工厂(XMLWorkflowFactory),工作流工厂的主要功能是管理流程定义文件,包括读取定义文件和修改定义文件的功能。通过resource
这个属性指明了采用通过从classpath中读取流程定义文件的方式,按照这个定义,接下来我们需要在classpath中创建一个名为workflows.xml的文件。
workflows.xml
<workflows>
<workflow name="mytest" type="resource" location="myworkflow.xml" />
</workflows>
myworkflow.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE workflow PUBLIC
"-//OpenSymphony Group//DTD OSWorkflow 2.7//EN"
"http://www.opensymphony.com/osworkflow/workflow_2_7.dtd">
<workflow>
<initial-actions>
<action id="1" name="Start Workflow">
<results>
<unconditional-result old-status="Finished"
status="Queued" step="1"/>
</results>
</action>
</initial-actions>
<steps>
<step id="1" name="First Draft">
<actions>
<action id="1" name="Start First Draft">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Queued</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished" status="Underway"
step="1" owner="${caller}"/>
</results>
</action>
<action id="2" name="Finish First Draft">
<restrict-to>
<conditions type="AND">
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Underway</arg>
</condition>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.AllowOwnerOnlyCondition
</arg>
</condition>
</conditions>
</restrict-to>
<results>
<unconditional-result old-status="Finished"
status="Queued" step="2"/>
</results>
</action>
</actions>
</step>
<step id="2" name="finished" />
</steps>
</workflow>
# 工作流测试
接下来,我们从测试工作流开始切入其源码分析。
OSWorkflow关于状态机的核心代码都在AbstractWorkflow中,其中EJBWorkflow 、 SOAPWorkflow、BasicWorkflow扩展自它。AbstractWorkflow实现了Workflow接口,该接口包含了工作流的核心方法,其中最重要的就是doAction方法。 为了简单起见,我们使用最基本的一种: BasicWorkflow。
String caller = "tester";
long workflowId = 1;
// 下一步是提供配置文件,在大多数情况下,只是简单的传递一个DefaultConfiguration实例:
// 现在我们已经创建并且配置好了一个workflow,接下来就是开始调用它了。
DefaultConfiguration config = new DefaultConfiguration();
workflow.setConfiguration(config);
// 首先我们需要调用initialize 方法来启动一个工作流程,这个方法有3个参数,workflow name (定义在workflows.xml里,通过workflow factory处理), action ID (我们要调用的初始化动作的ID,这里是1),和初始化变量。 因为在例子里面不需初始化变量,所以我们只是传递一个null,
Workflow workflow = new BasicWorkflow(caller);
workflowId = workflow.initialize("mytest", 1, null);
// 这是简单的调用第一个动作,工作流引擎根据指定的条件,改变状态到‘Underway’,并且保持在当前步骤。
workflow.doAction(workflowId, 1, null);
# 核心方法分析
根据前面工作流测试的代码,我们通过去分析其中几个核心方法的源码来了解OSWorkflow实现FSM的原理:
# initialize方法
initialize
方法是用来初始化一个工作流的。函数接收三个参数:一个字符串类型的workflowName,一个整数类型的initialAction和一个Map类型的inputs。
initialize初始化方法主要完成了以下几个功能:
- 首先,它从配置中获取工作流描述符WorkflowDescriptor对象wf,然后从持久化存储中获取WorkflowStore对象store,并创建一个新的工作流条目WorkflowEntry对象entry。
- 然后,将工作流条目的ID和名称放入inputs map中。创建一个新的属性集合PropertySet对象ps,它是从持久化存储中获取的,然后创建一个新的Map对象transientVars。
- 如果inputs不为空,将其所有元素都添加到transientVars中。然后,使用populateTransientMap方法填充transientVars。
- 接着,如果不能初始化工作流,则回滚上下文并抛出InvalidRoleException异常。
- 然后,获取初始动作描述符ActionDescriptor对象action,然后尝试转换工作流。如果在转换过程中发生WorkflowException异常,则回滚上下文并抛出该异常。
- 最后,获取工作流条目的ID,并返回该ID。
以下是initialize
方法的完整代码:
路径:https://github.com/ailohq/osworkflow/blob/master/src/main/java/com/opensymphony/workflow/AbstractWorkflow.java#L607
public long initialize(String workflowName, int initialAction, Map inputs) throws InvalidRoleException, InvalidInputException, WorkflowException {
WorkflowDescriptor wf = getConfiguration().getWorkflow(workflowName);
WorkflowStore store = getPersistence();
WorkflowEntry entry = store.createEntry(workflowName);
// Chanthu
inputs.put(WORKFLOW_ID_KEY, Long.toString(entry.getId()));
inputs.put(WORKFLOW_NAME_KEY, entry.getWorkflowName());
// start with a memory property set, but clone it after we have an ID
PropertySet ps = store.getPropertySet(entry.getId());
Map transientVars = new HashMap();
if (inputs != null) {
transientVars.putAll(inputs);
}
populateTransientMap(entry, transientVars, wf.getRegisters(), new Integer(initialAction), Collections.EMPTY_LIST, ps);
if (!canInitialize(workflowName, initialAction, transientVars, ps)) {
context.setRollbackOnly();
throw new InvalidRoleException("You are restricted from initializing this workflow");
}
ActionDescriptor action = wf.getInitialAction(initialAction);
try {
transitionWorkflow(entry, Collections.EMPTY_LIST, store, wf, action, transientVars, inputs, ps);
} catch (WorkflowException e) {
context.setRollbackOnly();
throw e;
}
long entryId = entry.getId();
// now clone the memory PS to the real PS
// PropertySetManager.clone(ps, store.getPropertySet(entryId));
return entryId;
}
前面的初始化代码,我们看到获取了初始动作后,开始调用transitionWorkflow
方法进行状态转换,接下来我们对这个方法进行分析。
# transitionWorkflow方法
transitionWorkflow方法
transitionWorkflow
函数主要目的是处理工作流实例的状态转换。以下是这个函数的主要功能:
清除或初始化状态缓存。
Map cache = (Map) stateCache.get(); if (cache != null) { cache.clear(); } else { stateCache.set(new HashMap()); }
获取当前步骤。
Step step = getCurrentStep(wf, action.getId(), currentSteps, transientVars, ps);
验证输入。
if (action.getValidators().size() > 0) { verifyInputs(entry, action.getValidators(), Collections.unmodifiableMap(transientVars), ps); }
如果动作有验证器,使用
verifyInputs
方法验证输入参数。执行步骤的后置函数。
if (step != null) { List stepPostFunctions = wf.getStep(step.getStepId()).getPostFunctions(); for (Iterator iterator = stepPostFunctions.iterator(); iterator.hasNext(); ) { FunctionDescriptor function = (FunctionDescriptor) iterator.next(); executeFunction(function, transientVars, ps); } }
如果当前步骤不为空,执行步骤的后置函数。
执行动作的前置函数。
List preFunctions = action.getPreFunctions(); for (Iterator iterator = preFunctions.iterator(); iterator.hasNext(); ) { FunctionDescriptor function = (FunctionDescriptor) iterator.next(); executeFunction(function, transientVars, ps); }
遍历动作的前置函数列表,执行每个前置函数。
处理条件结果
List conditionalResults = action.getConditionalResults(); List extraPreFunctions = null; List extraPostFunctions = null; ResultDescriptor[] theResults = new ResultDescriptor[1]; ....
这部分代码首先初始化条件结果、额外的前置函数和后置函数列表。然后遍历条件结果,检查是否满足条件。如果满足条件,执行额外的前置函数和后置函数。
处理分支(split)和连接(join)操作,见下面代码片段。
if (theResults[0].getSplit() != 0) { ... } else if (theResults[0].getJoin() != 0) { ... } else { ... }
7.1 处理分支(split)
处理分支(Split)的代码用于处理工作流中的分支逻辑。分支意味着工作流会根据条件分解为多个并行的子流程。
这里结合前面的spli的xml定义一起分析会更清晰。theResults是当前动作的结果,theResults[0]表示取第一个split元素。每个split里面有多个无条件结果,每个无条件结果都会指向新的步骤和状态。
<unconditional-result old-status="InProgress" status="Completed" split="2"/> ... <splits> <split id="2"> <unconditional-result old-status="Finished" step="2" status="Underway" /> <unconditional-result old-status="Finished" step="3" status="Underway" /> </split> </splits>
以下是对这部分代码的执行功能:
7.1.1 获取分支描述符和结果:
SplitDescriptor splitDesc = wf.getSplit(theResults[0].getSplit()); Collection results = splitDesc.getResults();
这部分代码首先获取分支描述符,然后从分支描述符中获取结果集合。
7.1.2 验证输入和收集函数:
for (Iterator iterator = results.iterator(); iterator.hasNext(); ) { ResultDescriptor resultDescriptor = (ResultDescriptor) iterator.next(); if (resultDescriptor.getValidators().size() > 0) { verifyInputs(entry, resultDescriptor.getValidators(), Collections.unmodifiableMap(transientVars), ps); } splitPreFunctions.addAll(resultDescriptor.getPreFunctions()); splitPostFunctions.addAll(resultDescriptor.getPostFunctions()); }
这部分代码遍历结果集合,对每个结果进行输入验证,并收集所有的前置函数和后置函数。
7.1.3 执行前置函数:
for (Iterator iterator = splitPreFunctions.iterator(); iterator.hasNext(); ) { FunctionDescriptor function = (FunctionDescriptor) iterator.next(); executeFunction(function, transientVars, ps); }
这部分代码遍历前置函数列表,执行每个前置函数。
7.1.4 创建新的当前步骤:
if (!action.isFinish()) { boolean moveFirst = true; theResults = new ResultDescriptor[results.size()]; results.toArray(theResults); for (Iterator iterator = results.iterator(); iterator.hasNext(); ) { ResultDescriptor resultDescriptor = (ResultDescriptor) iterator.next(); Step moveToHistoryStep = null; if (moveFirst) { moveToHistoryStep = step; } long[] previousIds = null; if (step != null) { previousIds = new long[]{step.getId()}; } createNewCurrentStep(resultDescriptor, entry, store, action.getId(), moveToHistoryStep, previousIds, transientVars, ps); moveFirst = false; } }
如果当前动作不是结束动作,这部分代码会为每个分支结果创建一个新的当前步骤。
createNewCurrentStep
方法用于创建新的当前步骤,并将旧的当前步骤移动到历史步骤中。7.1.5 执行后置函数:
for (Iterator iterator = splitPostFunctions.iterator(); iterator.hasNext(); ) { FunctionDescriptor function = (FunctionDescriptor) iterator.next(); executeFunction(function, transientVars, ps); }
这部分代码遍历后置函数列表,执行每个后置函数。
7.2 处理连接(join)
结合join的xml定义一起分析下面的代码:
<joins> <join id="1"> <conditions type="AND"> <condition type="beanshell"> <arg name="script"> "Finished".equals(jn.getStep(6).getStatus() && "Finished".equals(jn.getStep(8).getStatus()) </arg> </condition> </conditions> <unconditional-result old-status="Finished" status="Underway" step="2"/> </join> </joins>
7.2.1 获取连接描述符:
JoinDescriptor joinDesc = wf.getJoin(theResults[0].getJoin());
7.2.2 标记当前步骤为已完成并移至历史步骤:
step = store.markFinished(step, action.getId(), new Date(), theResults[0].getOldStatus(), context.getCaller()); store.moveToHistory(step);
这部分代码将当前步骤标记为已完成,并将其移至历史步骤中。
7.2.3 收集参与连接的步骤:
Collection joinSteps = new ArrayList(); joinSteps.add(step); for (Iterator iterator = currentSteps.iterator(); iterator.hasNext(); ) { Step currentStep = (Step) iterator.next(); if (currentStep.getId() != step.getId()) { StepDescriptor stepDesc = wf.getStep(currentStep.getStepId()); if (stepDesc.resultsInJoin(theResults[0].getJoin())) { joinSteps.add(currentStep); } } }
这部分代码首先将当前步骤添加到参与连接的步骤集合中,然后遍历当前的步骤,如果步骤的描述符表示它会结果在当前的连接,那么就将它添加到参与连接的步骤集合中。
7.2.4 检查历史步骤:
List historySteps = store.findHistorySteps(entry.getId()); for (Iterator i = historySteps.iterator(); i.hasNext(); ) { Step historyStep = (Step) i.next(); if (historyStep.getId() != step.getId()) { StepDescriptor stepDesc = wf.getStep(historyStep.getStepId()); if (stepDesc.resultsInJoin(theResults[0].getJoin())) { joinSteps.add(historyStep); } } }
7.2.5 检查连接条件:
JoinNodes jn = new JoinNodes(joinSteps); transientVars.put("jn", jn); if (passesConditions(null, joinDesc.getConditions(), Collections.unmodifiableMap(transientVars), ps, 0)) { ... }
这部分代码首先创建一个
JoinNodes
对象,然后检查是否满足连接的条件。如果满足条件,就执行后续的代码。7.2.6 验证输入并执行前置函数:
ResultDescriptor joinresult = joinDesc.getResult(); if (joinresult.getValidators().size() > 0) { verifyInputs(entry, joinresult.getValidators(), Collections.unmodifiableMap(transientVars), ps); } for (Iterator iterator = joinresult.getPreFunctions().iterator(); iterator.hasNext(); ) { FunctionDescriptor function = (FunctionDescriptor) iterator.next(); executeFunction(function, transientVars, ps); }
这部分代码首先获取连接的结果,然后验证输入参数,最后执行前置函数。
7.2.7 创建新的当前步骤:
long[] previousIds = new long[joinSteps.size()]; int i = 1; for (Iterator iterator = joinSteps.iterator(); iterator.hasNext(); ) { Step currentStep = (Step) iterator.next(); if (currentStep.getId() != step.getId()) { if (!historySteps.contains(currentStep)) { store.moveToHistory(currentStep); } previousIds[i] = currentStep.getId(); i++; } } if (!action.isFinish()) { previousIds[0] = step.getId(); theResults[0] = joinDesc.getResult(); createNewCurrentStep(joinDesc.getResult(), entry, store, action.getId(), null, previousIds, transientVars, ps); }
这部分代码首先创建一个数组用于保存参与连接的步骤的ID,然后遍历参与连接的步骤,将步骤移至历史步骤并保存其ID。然后,如果当前动作不是结束动作,就调用
createNewCurrentStep
方法创建新的当前步骤。7.2.8 执行后置函数:
for (Iterator iterator = joinresult.getPostFunctions().iterator(); iterator.hasNext(); ) { FunctionDescriptor function = (FunctionDescriptor) iterator.next(); executeFunction(function, transientVars, ps); }
这部分代码遍历后置函数列表,执行每个后置函数。
如果程序进入到另一个步骤step(不是split和join),结束当前步骤并将其移至历史步骤中去。若动作未结束,则创建新的步 骤并执行新步骤中的 pre-function前置函数。
long[] previousIds = null; if (step != null) { previousIds = new long[]{step.getId()}; } if (!action.isFinish()) { createNewCurrentStep(theResults[0], entry, store, action.getId(), step, previousIds, transientVars, ps); }
执行动作的后置函数
List postFunctions = action.getPostFunctions(); for (Iterator iterator = postFunctions.iterator(); iterator.hasNext(); ) { FunctionDescriptor function = (FunctionDescriptor) iterator.next(); executeFunction(function, transientVars, ps); }
遍历动作的后置函数列表,执行每个后置函数。
更新工作流实例状态
if ((wf.getInitialAction(action.getId()) != null) && (entry.getState() != WorkflowEntry.ACTIVATED)) { changeEntryState(entry.getId(), WorkflowEntry.ACTIVATED); }
如果执行的动作是初始动作且工作流实例的状态不是已激活(ACTIVATED),则更新工作流实例的状态为已激活。
检查是否完成工作流实例
if (action.isFinish()) { completeEntry(action, entry.getId(), getCurrentSteps(entry.getId()), WorkflowEntry.COMPLETED); return true; }
如果执行的动作是结束动作,调用
completeEntry
方法完成工作流实例,并返回true
。执行自动执行的动作
if (availableAutoActions.length > 0) { doAction(entry.getId(), availableAutoActions[0], inputs); }
这部分代码首先获取可用的自动执行动作,然后执行第一个自动执行动作。在OSWorkflow中,自动执行动作是指在当前动作完成后,系统会自动执行的后续动作。
返回
false
表示工作流实例尚未完成:return false;
如果代码执行到这里,说明工作流实例尚未完成,返回false
。
下面是完整的函数代码:
路径:https://github.com/ailohq/osworkflow/blob/master/src/main/java/com/opensymphony/workflow/AbstractWorkflow.java#L1025
/**
* @return true if the instance has been explicitly completed is this
* transition, false otherwise
* @throws WorkflowException
*/
protected boolean transitionWorkflow(WorkflowEntry entry, List currentSteps, WorkflowStore store, WorkflowDescriptor wf, ActionDescriptor action, Map transientVars, Map inputs, PropertySet ps)
throws WorkflowException {
// 清除或初始化状态缓存
Map cache = (Map) stateCache.get();
if (cache != null) {
cache.clear();
} else {
stateCache.set(new HashMap());
}
// 获取当前步骤
Step step = getCurrentStep(wf, action.getId(), currentSteps, transientVars, ps);
// 如果动作有验证器,使用verifyInputs方法验证输入参数
if (action.getValidators().size() > 0) {
verifyInputs(entry, action.getValidators(), Collections.unmodifiableMap(transientVars), ps);
}
// 执行步骤的后置函数
if (step != null) {
List stepPostFunctions = wf.getStep(step.getStepId()).getPostFunctions();
for (Iterator iterator = stepPostFunctions.iterator(); iterator.hasNext(); ) {
FunctionDescriptor function = (FunctionDescriptor) iterator.next();
executeFunction(function, transientVars, ps);
}
}
// 执行动作的前置函数
List preFunctions = action.getPreFunctions();
for (Iterator iterator = preFunctions.iterator(); iterator.hasNext(); ) {
FunctionDescriptor function = (FunctionDescriptor) iterator.next();
executeFunction(function, transientVars, ps);
}
// 这部分代码首先初始化条件结果、额外的前置函数和后置函数列表。然后遍历条件结果,检查是否满足条件。如果满足条件,执行额外的前置函数和后置函数
List conditionalResults = action.getConditionalResults();
List extraPreFunctions = null;
List extraPostFunctions = null;
ResultDescriptor[] theResults = new ResultDescriptor[1];
for (Iterator iterator = conditionalResults.iterator(); iterator.hasNext(); ) {
ConditionalResultDescriptor conditionalResult = (ConditionalResultDescriptor) iterator.next();
if (passesConditions(null, conditionalResult.getConditions(), Collections.unmodifiableMap(transientVars), ps, (step != null) ? step.getStepId() : (-1))) {
// if (evaluateExpression(conditionalResult.getCondition(),
// entry, wf.getRegisters(), null, transientVars)) {
theResults[0] = conditionalResult;
if (conditionalResult.getValidators().size() > 0) {
verifyInputs(entry, conditionalResult.getValidators(), Collections.unmodifiableMap(transientVars), ps);
}
extraPreFunctions = conditionalResult.getPreFunctions();
extraPostFunctions = conditionalResult.getPostFunctions();
break;
}
}
// use unconditional-result if a condition hasn't been met
if (theResults[0] == null) {
theResults[0] = action.getUnconditionalResult();
verifyInputs(entry, theResults[0].getValidators(), Collections.unmodifiableMap(transientVars), ps);
extraPreFunctions = theResults[0].getPreFunctions();
extraPostFunctions = theResults[0].getPostFunctions();
}
if (log.isDebugEnabled()) {
log.debug("theResult=" + theResults[0].getStep() + ' ' + theResults[0].getStatus());
}
if ((extraPreFunctions != null) && (extraPreFunctions.size() > 0)) {
// run any extra pre-functions that haven't been run already
for (Iterator iterator = extraPreFunctions.iterator(); iterator.hasNext(); ) {
FunctionDescriptor function = (FunctionDescriptor) iterator.next();
executeFunction(function, transientVars, ps);
}
}
// go to next step
if (theResults[0].getSplit() != 0) {
// 处理分支(Split)
SplitDescriptor splitDesc = wf.getSplit(theResults[0].getSplit());
Collection results = splitDesc.getResults();
List splitPreFunctions = new ArrayList();
List splitPostFunctions = new ArrayList();
// check all results in the split and verify the input against any
// validators specified
// also build up all the pre and post functions that should be
// called.
for (Iterator iterator = results.iterator(); iterator.hasNext(); ) {
ResultDescriptor resultDescriptor = (ResultDescriptor) iterator.next();
if (resultDescriptor.getValidators().size() > 0) {
verifyInputs(entry, resultDescriptor.getValidators(), Collections.unmodifiableMap(transientVars), ps);
}
splitPreFunctions.addAll(resultDescriptor.getPreFunctions());
splitPostFunctions.addAll(resultDescriptor.getPostFunctions());
}
// now execute the pre-functions
for (Iterator iterator = splitPreFunctions.iterator(); iterator.hasNext(); ) {
FunctionDescriptor function = (FunctionDescriptor) iterator.next();
executeFunction(function, transientVars, ps);
}
if (!action.isFinish()) {
// now make these steps...
boolean moveFirst = true;
theResults = new ResultDescriptor[results.size()];
results.toArray(theResults);
for (Iterator iterator = results.iterator(); iterator.hasNext(); ) {
ResultDescriptor resultDescriptor = (ResultDescriptor) iterator.next();
Step moveToHistoryStep = null;
if (moveFirst) {
moveToHistoryStep = step;
}
long[] previousIds = null;
if (step != null) {
previousIds = new long[]{step.getId()};
}
createNewCurrentStep(resultDescriptor, entry, store, action.getId(), moveToHistoryStep, previousIds, transientVars, ps);
moveFirst = false;
}
}
// now execute the post-functions
for (Iterator iterator = splitPostFunctions.iterator(); iterator.hasNext(); ) {
FunctionDescriptor function = (FunctionDescriptor) iterator.next();
executeFunction(function, transientVars, ps);
}
} else if (theResults[0].getJoin() != 0) {
// 处理连接(Join)
JoinDescriptor joinDesc = wf.getJoin(theResults[0].getJoin());
step = store.markFinished(step, action.getId(), new Date(), theResults[0].getOldStatus(), context.getCaller());
store.moveToHistory(step);
// ... now check to see if the expression evaluates
// (get only current steps that have a result to this join)
Collection joinSteps = new ArrayList();
joinSteps.add(step);
// currentSteps = store.findCurrentSteps(id); // shouldn't need to
// refresh the list
for (Iterator iterator = currentSteps.iterator(); iterator.hasNext(); ) {
Step currentStep = (Step) iterator.next();
if (currentStep.getId() != step.getId()) {
StepDescriptor stepDesc = wf.getStep(currentStep.getStepId());
if (stepDesc.resultsInJoin(theResults[0].getJoin())) {
joinSteps.add(currentStep);
}
}
}
// we also need to check history steps that were finished before
// this one
// that might be part of the join
List historySteps = store.findHistorySteps(entry.getId());
for (Iterator i = historySteps.iterator(); i.hasNext(); ) {
Step historyStep = (Step) i.next();
if (historyStep.getId() != step.getId()) {
StepDescriptor stepDesc = wf.getStep(historyStep.getStepId());
if (stepDesc.resultsInJoin(theResults[0].getJoin())) {
joinSteps.add(historyStep);
}
}
}
JoinNodes jn = new JoinNodes(joinSteps);
transientVars.put("jn", jn);
// todo verify that 0 is the right value for currentstep here
if (passesConditions(null, joinDesc.getConditions(), Collections.unmodifiableMap(transientVars), ps, 0)) {
// move the rest without creating a new step ...
ResultDescriptor joinresult = joinDesc.getResult();
if (joinresult.getValidators().size() > 0) {
verifyInputs(entry, joinresult.getValidators(), Collections.unmodifiableMap(transientVars), ps);
}
// now execute the pre-functions
for (Iterator iterator = joinresult.getPreFunctions().iterator(); iterator.hasNext(); ) {
FunctionDescriptor function = (FunctionDescriptor) iterator.next();
executeFunction(function, transientVars, ps);
}
long[] previousIds = new long[joinSteps.size()];
int i = 1;
for (Iterator iterator = joinSteps.iterator(); iterator.hasNext(); ) {
Step currentStep = (Step) iterator.next();
if (currentStep.getId() != step.getId()) {
// if this is already a history step (eg, for all join
// steps completed prior to this one),
// we don't move it, since it's already history.
if (!historySteps.contains(currentStep)) {
store.moveToHistory(currentStep);
}
previousIds[i] = currentStep.getId();
i++;
}
}
if (!action.isFinish()) {
// ... now finish this step normally
previousIds[0] = step.getId();
theResults[0] = joinDesc.getResult();
// we pass in null for the current step since we've already
// moved it to history above
createNewCurrentStep(joinDesc.getResult(), entry, store, action.getId(), null, previousIds, transientVars, ps);
}
// now execute the post-functions
for (Iterator iterator = joinresult.getPostFunctions().iterator(); iterator.hasNext(); ) {
FunctionDescriptor function = (FunctionDescriptor) iterator.next();
executeFunction(function, transientVars, ps);
}
}
} else {
// normal finish, no splits or joins
long[] previousIds = null;
if (step != null) {
previousIds = new long[]{step.getId()};
}
if (!action.isFinish()) {
createNewCurrentStep(theResults[0], entry, store, action.getId(), step, previousIds, transientVars, ps);
}
}
// postFunctions (BOTH)
if (extraPostFunctions != null) {
for (Iterator iterator = extraPostFunctions.iterator(); iterator.hasNext(); ) {
FunctionDescriptor function = (FunctionDescriptor) iterator.next();
executeFunction(function, transientVars, ps);
}
}
// 执行后置函
List postFunctions = action.getPostFunctions();
for (Iterator iterator = postFunctions.iterator(); iterator.hasNext(); ) {
FunctionDescriptor function = (FunctionDescriptor) iterator.next();
executeFunction(function, transientVars, ps);
}
// if executed action was an initial action then workflow is activated
// 如果执行的动作是初始动作且工作流实例的状态不是已激活(ACTIVATED),则更新工作流实例的状态为已激活
if ((wf.getInitialAction(action.getId()) != null) && (entry.getState() != WorkflowEntry.ACTIVATED)) {
changeEntryState(entry.getId(), WorkflowEntry.ACTIVATED);
}
// 如果执行的动作是结束动作,调用completeEntry方法完成工作流实例,并返回true
if (action.isFinish()) {
completeEntry(action, entry.getId(), getCurrentSteps(entry.getId()), WorkflowEntry.COMPLETED);
return true;
}
// 执行自动执行的动作(调用上一步的doAction方法)
int[] availableAutoActions = getAvailableAutoActions(entry.getId(), inputs);
if (availableAutoActions.length > 0) {
doAction(entry.getId(), availableAutoActions[0], inputs);
}
return false;
}
# doAction方法
doAction
函数的主要目的是执行一个工作流中的某个动作。doAction
方法负责处理状态转换、执行相关的动作(Action)、调用条件判断(Conditions)以及触发后续步骤(Steps)等。
以下是对该函数的分段解释:
- 获取持久化的工作流存储对象,并根据传入的id查找对应的工作流实体(WorkflowEntry)。
WorkflowStore store = getPersistence();
WorkflowEntry entry = store.findEntry(id);
- 将工作流实体的ID和名称放入输入映射中。
inputs.put(WORKFLOW_ID_KEY, Long.toString(entry.getId()));
inputs.put(WORKFLOW_NAME_KEY, entry.getWorkflowName());
- 判断工作流实体的状态是否为激活状态,如果不是,则直接返回。
if (entry.getState() != WorkflowEntry.ACTIVATED) {
return;
}
- 获取工作流描述符对象(WorkflowDescriptor),并查找当前步骤列表。
WorkflowDescriptor wf = getConfiguration().getWorkflow(entry.getWorkflowName());
List currentSteps = store.findCurrentSteps(id);
- 初始化动作描述符(ActionDescriptor)对象、属性集(PropertySet)对象和临时变量映射。
ActionDescriptor action = null;
PropertySet ps = store.getPropertySet(id);
Map transientVars = new HashMap();
- 将输入映射中的内容添加到临时变量映射中,并调用
populateTransientMap
方法填充临时变量映射。
if (inputs != null) {
transientVars.putAll(inputs);
}
populateTransientMap(entry, transientVars, wf.getRegisters(), new Integer(actionId), currentSteps, ps);
- 遍历全局动作列表,查找与传入动作ID匹配的动作描述符,并检查该动作是否可用。如果可用,则将
validAction
设置为true。
boolean validAction = false;
for (Iterator gIter = wf.getGlobalActions().iterator(); !validAction && gIter.hasNext(); ) {
ActionDescriptor actionDesc = (ActionDescriptor) gIter.next();
if (actionDesc.getId() == actionId) {
action = actionDesc;
if (isActionAvailable(action, transientVars, ps, 0)) {
validAction = true;
}
}
}
- 如果在全局动作列表中未找到有效动作,则遍历当前步骤列表,并在每个步骤的动作列表中查找与传入动作ID匹配的动作描述符。同样地,检查该动作是否可用,如果可用,则将
validAction
设置为true。
for (Iterator iter = currentSteps.iterator(); !validAction && iter.hasNext(); ) {
Step step = (Step) iter.next();
StepDescriptor s = wf.getStep(step.getStepId());
for (Iterator iterator = s.getActions().iterator(); !validAction && iterator.hasNext(); ) {
ActionDescriptor actionDesc = (ActionDescriptor) iterator.next();
if (actionDesc.getId() == actionId) {
action = actionDesc;
if (isActionAvailable(action, transientVars, ps, s.getId())) {
validAction = true;
}
}
}
}
- 如果未找到有效动作,则抛出InvalidActionException异常。
if (!validAction) {
throw new InvalidActionException("Action " + actionId + " is invalid");
}
- 调用
transitionWorkflow
方法尝试执行工作流转换。如果工作流未显式完成,则调用checkImplicitFinish
方法检查是否可以隐式完成。
try {
if (!transitionWorkflow(entry, currentSteps, store, wf, action, transientVars, inputs, ps)) {
checkImplicitFinish(action, id);
}
} catch (WorkflowException e) {
context.setRollbackOnly();
throw e;
}
整个函数的执行流程是:首先获取工作流实体和描述符,然后遍历全局动作和当前步骤的动作,查找有效动作。找到有效动作后,执行工作流转换,最后检查工作流是否完成。
下面是函数完整代码:
路径:https://github.com/ailohq/osworkflow/blob/master/src/main/java/com/opensymphony/workflow/AbstractWorkflow.java#L515
public void doAction(long id, int actionId, Map inputs) throws WorkflowException {
// 获取工作流存储和工作流实例
WorkflowStore store = getPersistence();
WorkflowEntry entry = store.findEntry(id);
inputs.put(WORKFLOW_ID_KEY, Long.toString(entry.getId()));
inputs.put(WORKFLOW_NAME_KEY, entry.getWorkflowName());
// 如果工作流实例的状态不是已激活(ACTIVATED),则直接返回,不执行后续的动作。
if (entry.getState() != WorkflowEntry.ACTIVATED) {
return;
}
// 获取工作流描述和当前步骤
WorkflowDescriptor wf = getConfiguration().getWorkflow(entry.getWorkflowName());
List currentSteps = store.findCurrentSteps(id);
ActionDescriptor action = null;
PropertySet ps = store.getPropertySet(id);
Map transientVars = new HashMap();
if (inputs != null) {
transientVars.putAll(inputs);
}
populateTransientMap(entry, transientVars, wf.getRegisters(), new Integer(actionId), currentSteps, ps);
// 这部分代码首先检查全局动作,如果动作ID匹配并且动作可用,就将validAction设置为true
boolean validAction = false;
for (Iterator gIter = wf.getGlobalActions().iterator(); !validAction && gIter.hasNext(); ) {
ActionDescriptor actionDesc = (ActionDescriptor) gIter.next();
if (actionDesc.getId() == actionId) {
action = actionDesc;
if (isActionAvailable(action, transientVars, ps, 0)) {
validAction = true;
}
}
}
// 如果全局动作检查不通过,检查当前步骤的动作
for (Iterator iter = currentSteps.iterator(); !validAction && iter.hasNext(); ) {
Step step = (Step) iter.next();
StepDescriptor s = wf.getStep(step.getStepId());
for (Iterator iterator = s.getActions().iterator(); !validAction && iterator.hasNext(); ) {
ActionDescriptor actionDesc = (ActionDescriptor) iterator.next();
if (actionDesc.getId() == actionId) {
action = actionDesc;
if (isActionAvailable(action, transientVars, ps, s.getId())) {
validAction = true;
}
}
}
}
// 如果动作无效,抛出异常
if (!validAction) {
throw new InvalidActionException("Action " + actionId + " is invalid");
}
try {
// 调用transitionWorkflow方法执行工作流的转换
// 尝试执行工作流的转换,如果转换失败,检查是否可以隐式完成。如果在执行转换过程中发生异常,将上下文设置为只回滚,然后抛出异常
if (!transitionWorkflow(entry, currentSteps, store, wf, action, transientVars, inputs, ps)) {
checkImplicitFinish(action, id);
}
} catch (WorkflowException e) {
context.setRollbackOnly();
throw e;
}
}
# createNewCurrentStep方法
这个函数的主要目标是在工作流中创建一个新的当前步骤。以下是对这个函数的分段解释:
这个函数首先获取结果描述符中的下一个步骤。如果下一个步骤为-1,那么它将检查当前步骤是否存在。如果当前步骤存在,那么下一个步骤就是当前步骤的ID,否则,将抛出一个异常,因为请求的新的当前步骤与当前步骤相同,但是没有指定当前步骤。
int nextStep = theResult.getStep(); if (nextStep == -1) { if (currentStep != null) { nextStep = currentStep.getStepId(); } else { throw new StoreException("Illegal argument: requested new current step same as current step, but current step not specified"); } }
函数获取结果描述符中的所有者,并使用变量解析器将所有者中的变量翻译为实际的值。
String owner = theResult.getOwner(); VariableResolver variableResolver = getConfiguration().getVariableResolver(); if (owner != null) { Object o = variableResolver.translateVariables(owner, transientVars, ps); owner = (o != null) ? o.toString() : null; }
函数获取结果描述符中的旧状态和新状态,并使用变量解析器将这些状态中的变量翻译为实际的值。
String oldStatus = theResult.getOldStatus(); oldStatus = variableResolver.translateVariables(oldStatus, transientVars, ps).toString(); String status = theResult.getStatus(); status = variableResolver.translateVariables(status, transientVars, ps).toString();
如果当前步骤存在,那么函数将标记当前步骤为已完成,并将当前步骤移动到历史记录中。
if (currentStep != null) { store.markFinished(currentStep, actionId, new Date(), oldStatus, context.getCaller()); store.moveToHistory(currentStep); }
函数创建开始日期和可能的截止日期。如果结果描述符中的截止日期不为空,那么函数将使用变量解析器将截止日期中的变量翻译为实际的值。
Date startDate = new Date(); Date dueDate = null; if ((theResult.getDueDate() != null) && (theResult.getDueDate().length() > 0)) { Object dueDateObject = variableResolver.translateVariables(theResult.getDueDate(), transientVars, ps); // ...省略了一些代码... }
函数在工作流存储中创建新的当前步骤,并将新步骤添加到transientVars映射中。
Step newStep = store.createCurrentStep(entry.getId(), nextStep, owner, startDate, dueDate, status, previousIds); transientVars.put("createdStep", newStep);
如果previousIds参数不为null并且长度为0,同时当前步骤为null,那么函数将创建一个包含新步骤的列表,并将这个列表添加到transientVars映射中。
if ((previousIds != null) && (previousIds.length == 0) && (currentStep == null)) { List currentSteps = new ArrayList(); currentSteps.add(newStep); transientVars.put("currentSteps", new ArrayList(currentSteps)); }
函数从transientVars映射中获取工作流描述符,并从描述符中获取下一个步骤的描述符。
WorkflowDescriptor descriptor = (WorkflowDescriptor) transientVars.get("descriptor"); StepDescriptor step = descriptor.getStep(nextStep); if (step == null) { throw new WorkflowException("step #" + nextStep + " does not exist"); }
函数获取下一个步骤的描述符中的前置函数列表,并执行这些函数。
List preFunctions = step.getPreFunctions(); for (Iterator iterator = preFunctions.iterator(); iterator.hasNext(); ) { FunctionDescriptor function = (FunctionDescriptor) iterator.next(); executeFunction(function, transientVars, ps); }
最后,函数返回新的当前步骤。如果在函数执行过程中发生了异常,那么函数将设置回滚标志,并抛出异常。
try { // 省略..... return newStep; } catch (WorkflowException e) { context.setRollbackOnly(); throw e; }
下面是完整函数代码:
路径:https://github.com/ailohq/osworkflow/blob/master/src/main/java/com/opensymphony/workflow/AbstractWorkflow.java#L1449
private Step createNewCurrentStep(ResultDescriptor theResult, WorkflowEntry entry, WorkflowStore store, int actionId, Step currentStep, long[] previousIds, Map transientVars, PropertySet ps)
throws WorkflowException {
try {
int nextStep = theResult.getStep();
if (nextStep == -1) {
if (currentStep != null) {
nextStep = currentStep.getStepId();
} else {
throw new StoreException("Illegal argument: requested new current step same as current step, but current step not specified");
}
}
if (log.isDebugEnabled()) {
log.debug("Outcome: stepId=" + nextStep + ", status=" + theResult.getStatus() + ", owner=" + theResult.getOwner() + ", actionId=" + actionId + ", currentStep="
+ ((currentStep != null) ? currentStep.getStepId() : 0));
}
if (previousIds == null) {
previousIds = new long[0];
}
String owner = theResult.getOwner();
VariableResolver variableResolver = getConfiguration().getVariableResolver();
if (owner != null) {
Object o = variableResolver.translateVariables(owner, transientVars, ps);
owner = (o != null) ? o.toString() : null;
}
String oldStatus = theResult.getOldStatus();
oldStatus = variableResolver.translateVariables(oldStatus, transientVars, ps).toString();
String status = theResult.getStatus();
status = variableResolver.translateVariables(status, transientVars, ps).toString();
if (currentStep != null) {
store.markFinished(currentStep, actionId, new Date(), oldStatus, context.getCaller());
store.moveToHistory(currentStep);
// store.moveToHistory(actionId, new Date(), currentStep,
// oldStatus, context.getCaller());
}
// construct the start date and optional due date
Date startDate = new Date();
Date dueDate = null;
if ((theResult.getDueDate() != null) && (theResult.getDueDate().length() > 0)) {
Object dueDateObject = variableResolver.translateVariables(theResult.getDueDate(), transientVars, ps);
if (dueDateObject instanceof Date) {
dueDate = (Date) dueDateObject;
} else if (dueDateObject instanceof String) {
long offset = 0;
try {
offset = Long.parseLong((String) dueDateObject);
} catch (NumberFormatException e) {
}
if (offset > 0) {
dueDate = new Date(startDate.getTime() + offset);
}
} else if (dueDateObject instanceof Number) {
Number num = (Number) dueDateObject;
long offset = num.longValue();
if (offset > 0) {
dueDate = new Date(startDate.getTime() + offset);
}
}
}
Step newStep = store.createCurrentStep(entry.getId(), nextStep, owner, startDate, dueDate, status, previousIds);
transientVars.put("createdStep", newStep);
if ((previousIds != null) && (previousIds.length == 0) && (currentStep == null)) {
// At this point, it must be a brand new workflow, so we'll
// overwrite the empty currentSteps
// with an array of just this current step
List currentSteps = new ArrayList();
currentSteps.add(newStep);
transientVars.put("currentSteps", new ArrayList(currentSteps));
}
WorkflowDescriptor descriptor = (WorkflowDescriptor) transientVars.get("descriptor");
StepDescriptor step = descriptor.getStep(nextStep);
if (step == null) {
throw new WorkflowException("step #" + nextStep + " does not exist");
}
List preFunctions = step.getPreFunctions();
for (Iterator iterator = preFunctions.iterator(); iterator.hasNext(); ) {
FunctionDescriptor function = (FunctionDescriptor) iterator.next();
executeFunction(function, transientVars, ps);
}
return newStep;
} catch (WorkflowException e) {
context.setRollbackOnly();
throw e;
}
}
}